import {extend, defer, requestAnimationFrame} from '../../utils/core'
import DefaultViewManager from '../default'
import Snap from '../helpers/snap'
import {EVENTS} from '../../utils/constants'
import debounce from 'lodash/debounce'

class ContinuousViewManager extends DefaultViewManager {
	constructor(options) {
		super(options)

		this.name = 'continuous'

		this.settings = extend(this.settings || {}, {
			infinite: true,
			overflow: undefined,
			axis: undefined,
			flow: 'scrolled',
			offset: 500,
			offsetDelta: 250,
			width: undefined,
			height: undefined,
			snap: false,
			afterScrolledTimeout: 10
		})

		extend(this.settings, options.settings || {})

		// Gap can be 0, but defaults doesn't handle that
		if (options.settings.gap != 'undefined' && options.settings.gap === 0) {
			this.settings.gap = options.settings.gap
		}

		this.viewSettings = {
			ignoreClass: this.settings.ignoreClass,
			axis: this.settings.axis,
			flow: this.settings.flow,
			layout: this.layout,
			width: 0,
			height: 0,
			forceEvenPages: false
		}

		this.scrollTop = 0
		this.scrollLeft = 0
	}

	display(section, target) {
		return DefaultViewManager.prototype.display.call(this, section, target)
				.then(function () {
					return this.fill()
				}.bind(this))
	}

	fill(_full) {
		var full = _full || new defer()

		this.q.enqueue(() => {
			return this.check()
		}).then((result) => {
			if (result) {
				this.fill(full)
			} else {
				full.resolve()
			}
		})

		return full.promise
	}

	moveTo(offset) {
		// var bounds = this.stage.bounds();
		// var dist = Math.floor(offset.top / bounds.height) * bounds.height;
		var distX = 0,
				distY = 0

		var offsetX = 0,
				offsetY = 0

		if (!this.isPaginated) {
			distY = offset.top
			offsetY = offset.top + this.settings.offsetDelta
		} else {
			distX = Math.floor(offset.left / this.layout.delta) * this.layout.delta
			offsetX = distX + this.settings.offsetDelta
		}

		if (distX > 0 || distY > 0) {
			this.scrollBy(distX, distY, true)
		}
	}

	afterResized(view) {
		this.emit(EVENTS.MANAGERS.RESIZE, view.section)
	}

	// Remove Previous Listeners if present
	removeShownListeners(view) {

		// view.off("shown", this.afterDisplayed);
		// view.off("shown", this.afterDisplayedAbove);
		view.onDisplayed = function () {
		}

	}

	add(section) {
		var view = this.createView(section)

		this.views.append(view)

		view.on(EVENTS.VIEWS.RESIZED, (bounds) => {
			view.expanded = true
		})

		view.on(EVENTS.VIEWS.AXIS, (axis) => {
			this.updateAxis(axis)
		})

		// view.on(EVENTS.VIEWS.SHOWN, this.afterDisplayed.bind(this));
		view.onDisplayed = this.afterDisplayed.bind(this)
		view.onResize = this.afterResized.bind(this)

		return view.display(this.request)
	}

	append(section) {
		var view = this.createView(section)

		view.on(EVENTS.VIEWS.RESIZED, (bounds) => {
			view.expanded = true
		})

		view.on(EVENTS.VIEWS.AXIS, (axis) => {
			this.updateAxis(axis)
		})

		this.views.append(view)

		view.onDisplayed = this.afterDisplayed.bind(this)

		return view
	}

	prepend(section) {
		var view = this.createView(section)

		view.on(EVENTS.VIEWS.RESIZED, (bounds) => {
			this.counter(bounds)
			view.expanded = true
		})

		view.on(EVENTS.VIEWS.AXIS, (axis) => {
			this.updateAxis(axis)
		})

		this.views.prepend(view)

		view.onDisplayed = this.afterDisplayed.bind(this)

		return view
	}

	counter(bounds) {
		if (this.settings.axis === 'vertical') {
			this.scrollBy(0, bounds.heightDelta, true)
		} else {
			this.scrollBy(bounds.widthDelta, 0, true)
		}

	}

	update(_offset) {
		var container = this.bounds()
		var views = this.views.all()
		var viewsLength = views.length
		var visible = []
		var offset = typeof _offset != 'undefined' ? _offset : (this.settings.offset || 0)
		var isVisible
		var view

		var updating = new defer()
		var promises = []
		for (var i = 0; i < viewsLength; i++) {
			view = views[i]

			isVisible = this.isVisible(view, offset, offset, container)

			if (isVisible === true) {
				// console.log("visible " + view.index);

				if (!view.displayed) {
					let displayed = view.display(this.request)
							.then(function (view) {
								view.show()
							}, (err) => {
								view.hide()
							})
					promises.push(displayed)
				} else {
					view.show()
				}
				visible.push(view)
			} else {
				this.q.enqueue(view.destroy.bind(view))
				// console.log("hidden " + view.index);

				clearTimeout(this.trimTimeout)
				this.trimTimeout = setTimeout(function () {
					this.q.enqueue(this.trim.bind(this))
				}.bind(this), 250)
			}

		}

		if (promises.length) {
			return Promise.all(promises)
					.catch((err) => {
						updating.reject(err)
					})
		} else {
			updating.resolve()
			return updating.promise
		}

	}

	check(_offsetLeft, _offsetTop) {
		var checking = new defer()
		var newViews = []

		var horizontal = (this.settings.axis === 'horizontal')
		var delta = this.settings.offset || 0

		if (_offsetLeft && horizontal) {
			delta = _offsetLeft
		}

		if (_offsetTop && !horizontal) {
			delta = _offsetTop
		}

		var bounds = this._bounds // bounds saved this until resize

		let rtl = this.settings.direction === 'rtl'
		let dir = horizontal && rtl ? -1 : 1 //RTL reverses scrollTop

		var offset = horizontal ? this.scrollLeft : this.scrollTop * dir
		var visibleLength = horizontal ? Math.floor(bounds.width) : bounds.height
		var contentLength = horizontal ? this.container.scrollWidth : this.container.scrollHeight

		let prepend = () => {
			let first = this.views.first()
			let prev = first && first.section.prev()

			if (prev) {
				newViews.push(this.prepend(prev))
			}
		}

		let append = () => {
			let last = this.views.last()
			let next = last && last.section.next()

			if (next) {
				newViews.push(this.append(next))
			}

		}

		if (offset + visibleLength + delta >= contentLength) {
			if (horizontal && rtl) {
				prepend()
			} else {
				append()
			}
		}

		if (offset - delta < 0) {
			if (horizontal && rtl) {
				append()
			} else {
				prepend()
			}
		}

		let promises = newViews.map((view) => {
			return view.displayed
		})

		if (newViews.length) {
			return Promise.all(promises)
					.then(() => {
						if (this.layout.name === 'pre-paginated' && this.layout.props.spread) {
							return this.check()
						}
					})
					.then(() => {
						// Check to see if anything new is on screen after rendering
						return this.update(delta)
					}, (err) => {
						return err
					})
		} else {
			this.q.enqueue(function () {
				this.update()
			}.bind(this))
			checking.resolve(false)
			return checking.promise
		}


	}

	trim() {
		var task = new defer()
		var displayed = this.views.displayed()
		var first = displayed[0]
		var last = displayed[displayed.length - 1]
		var firstIndex = this.views.indexOf(first)
		var lastIndex = this.views.indexOf(last)
		var above = this.views.slice(0, firstIndex)
		var below = this.views.slice(lastIndex + 1)

		// Erase all but last above
		for (var i = 0; i < above.length - 1; i++) {
			this.erase(above[i], above)
		}

		// Erase all except first below
		for (var j = 1; j < below.length; j++) {
			this.erase(below[j])
		}

		task.resolve()
		return task.promise
	}

	erase(view, above) { //Trim

		var prevTop
		var prevLeft

		if (!this.settings.fullsize) {
			prevTop = this.container.scrollTop
			prevLeft = this.container.scrollLeft
		} else {
			prevTop = window.scrollY
			prevLeft = window.scrollX
		}

		var bounds = view.bounds()

		this.views.remove(view)

		if (above) {
			if (this.settings.axis === 'vertical') {
				this.scrollTo(0, prevTop - bounds.height, true)
			} else {
				this.scrollTo(prevLeft - Math.floor(bounds.width), 0, true)
			}
		}

	}

	addEventListeners(stage) {

		window.addEventListener('unload', function (e) {
			this.ignore = true
			// this.scrollTo(0,0);
			this.destroy()
		}.bind(this))

		this.addScrollListeners()

		if (this.isPaginated && this.settings.snap) {
			this.snapper = new Snap(this, this.settings.snap && (typeof this.settings.snap === 'object') && this.settings.snap)
		}
	}

	addScrollListeners() {
		var scroller

		this.tick = requestAnimationFrame

		if (!this.settings.fullsize) {
			this.prevScrollTop = this.container.scrollTop
			this.prevScrollLeft = this.container.scrollLeft
		} else {
			this.prevScrollTop = window.scrollY
			this.prevScrollLeft = window.scrollX
		}

		this.scrollDeltaVert = 0
		this.scrollDeltaHorz = 0

		if (!this.settings.fullsize) {
			scroller = this.container
			this.scrollTop = this.container.scrollTop
			this.scrollLeft = this.container.scrollLeft
		} else {
			scroller = window
			this.scrollTop = window.scrollY
			this.scrollLeft = window.scrollX
		}

		this._onScroll = this.onScroll.bind(this)
		scroller.addEventListener('scroll', this._onScroll)
		this._scrolled = debounce(this.scrolled.bind(this), 30)
		// this.tick.call(window, this.onScroll.bind(this));

		this.didScroll = false

	}

	removeEventListeners() {
		var scroller

		if (!this.settings.fullsize) {
			scroller = this.container
		} else {
			scroller = window
		}

		scroller.removeEventListener('scroll', this._onScroll)
		this._onScroll = undefined
	}

	onScroll() {
		let scrollTop
		let scrollLeft
		let dir = this.settings.direction === 'rtl' ? -1 : 1

		if (!this.settings.fullsize) {
			scrollTop = this.container.scrollTop
			scrollLeft = this.container.scrollLeft
		} else {
			scrollTop = window.scrollY * dir
			scrollLeft = window.scrollX * dir
		}

		this.scrollTop = scrollTop
		this.scrollLeft = scrollLeft

		if (!this.ignore) {

			this._scrolled()

		} else {
			this.ignore = false
		}

		this.scrollDeltaVert += Math.abs(scrollTop - this.prevScrollTop)
		this.scrollDeltaHorz += Math.abs(scrollLeft - this.prevScrollLeft)

		this.prevScrollTop = scrollTop
		this.prevScrollLeft = scrollLeft

		clearTimeout(this.scrollTimeout)
		this.scrollTimeout = setTimeout(function () {
			this.scrollDeltaVert = 0
			this.scrollDeltaHorz = 0
		}.bind(this), 150)

		clearTimeout(this.afterScrolled)

		this.didScroll = false

	}

	scrolled() {

		this.q.enqueue(function () {
			this.check()
		}.bind(this))

		this.emit(EVENTS.MANAGERS.SCROLL, {
			top: this.scrollTop,
			left: this.scrollLeft
		})

		clearTimeout(this.afterScrolled)
		this.afterScrolled = setTimeout(function () {

			// Don't report scroll if we are about the snap
			if (this.snapper && this.snapper.supportsTouch && this.snapper.needsSnap()) {
				return
			}

			this.emit(EVENTS.MANAGERS.SCROLLED, {
				top: this.scrollTop,
				left: this.scrollLeft
			})

		}.bind(this), this.settings.afterScrolledTimeout)
	}

	next() {

		let dir = this.settings.direction
		let delta = this.layout.props.name === 'pre-paginated' &&
		this.layout.props.spread ? this.layout.props.delta * 2 : this.layout.props.delta

		if (!this.views.length) return

		if (this.isPaginated && this.settings.axis === 'horizontal') {

			this.scrollBy(delta, 0, true)

		} else {

			this.scrollBy(0, this.layout.height, true)

		}

		this.q.enqueue(function () {
			this.check()
		}.bind(this))
	}

	prev() {

		let dir = this.settings.direction
		let delta = this.layout.props.name === 'pre-paginated' &&
		this.layout.props.spread ? this.layout.props.delta * 2 : this.layout.props.delta

		if (!this.views.length) return

		if (this.isPaginated && this.settings.axis === 'horizontal') {

			this.scrollBy(-delta, 0, true)

		} else {

			this.scrollBy(0, -this.layout.height, true)

		}

		this.q.enqueue(function () {
			this.check()
		}.bind(this))
	}

	// updateAxis(axis, forceUpdate){
	//
	// 	super.updateAxis(axis, forceUpdate);
	//
	// 	if (axis === "vertical") {
	// 		this.settings.infinite = true;
	// 	} else {
	// 		this.settings.infinite = false;
	// 	}
	// }

	updateFlow(flow) {
		if (this.rendered && this.snapper) {
			this.snapper.destroy()
			this.snapper = undefined
		}

		super.updateFlow(flow, 'scroll')

		if (this.rendered && this.isPaginated && this.settings.snap) {
			this.snapper = new Snap(this, this.settings.snap && (typeof this.settings.snap === 'object') && this.settings.snap)
		}
	}

	destroy() {
		super.destroy()

		if (this.snapper) {
			this.snapper.destroy()
		}
	}

}

export default ContinuousViewManager
